package com.lifeonwalden.copula.func;

import com.lifeonwalden.copula.constant.CopulaConstants;

import java.math.BigDecimal;
import java.math.RoundingMode;

public interface ArchimedeanGenerators {
    static void boundsConditions(BigDecimal x) {
        if (x.compareTo(BigDecimal.ZERO) < 0 || x.compareTo(BigDecimal.ONE) > 0) {
            throw new RuntimeException("Unable to compute generator for x equals to ".concat(x.toPlainString()));
        }
    }

    static void claytonBoundsConditions(BigDecimal theta) {
        if (BigDecimal.ZERO.compareTo(theta) == 0) {
            throw new RuntimeException("The parameter of a Clayton copula must not be equal to 0.");
        }

        if (theta.compareTo(CopulaConstants.MINUS_ONE) < 0) {
            throw new RuntimeException("The parameter of a Clayton copula must be greater than -1 and different from 0.");
        }
    }

    static BigDecimal claytonGenerator(BigDecimal x, BigDecimal theta) {
        boundsConditions(x);
        claytonBoundsConditions(theta);

        return BigDecimal.ONE
                .divide(theta, 16, RoundingMode.DOWN)
                .multiply(BigDecimal.valueOf(Math.pow(x.doubleValue(), CopulaConstants.MINUS_ONE.multiply(theta).doubleValue())).subtract(BigDecimal.ONE));
    }

    static BigDecimal claytonGeneratorInvert(BigDecimal x, BigDecimal theta) {
        claytonBoundsConditions(theta);

        return BigDecimal.valueOf(
                Math.pow(
                        BigDecimal.ONE.add(theta.multiply(x)).doubleValue(),
                        CopulaConstants.MINUS_ONE.divide(theta, 16, RoundingMode.DOWN).doubleValue()));
    }

    static void gumbelBoundsConditions(BigDecimal theta) {
        if (theta.compareTo(BigDecimal.ONE) < 0) {
            throw new RuntimeException("The parameter of a Gumbel copula must be greater than 1.");
        }
    }

    static BigDecimal gumbelGenerator(BigDecimal x, BigDecimal theta) {
        boundsConditions(x);
        gumbelBoundsConditions(theta);

        return BigDecimal.valueOf(Math.pow(-1 * Math.log(x.doubleValue()), theta.doubleValue()));
    }

    static BigDecimal gumbelGeneratorInvert(BigDecimal x, BigDecimal theta) {
        gumbelBoundsConditions(theta);

        return BigDecimal.valueOf(Math.exp(-1 * Math.pow(x.doubleValue(), BigDecimal.ONE.divide(theta, 16, RoundingMode.DOWN).doubleValue())));
    }

    static void frankBoundsConditions(BigDecimal theta) {
        if (theta.compareTo(BigDecimal.ZERO) == 0) {
            throw new RuntimeException("The parameter of a Frank copula must not be equal to 0.");
        }
    }

    static BigDecimal frankGenerator(BigDecimal x, BigDecimal theta) {
        boundsConditions(x);
        frankBoundsConditions(theta);

        return BigDecimal.valueOf(
                -1 * Math.log(
                        BigDecimal.valueOf(Math.exp(-1 * theta.doubleValue() * x.doubleValue()) - 1)
                                .divide(BigDecimal.valueOf(Math.exp(-1 * theta.doubleValue()) - 1), 16, RoundingMode.DOWN)
                                .doubleValue()));
    }

    static BigDecimal frankGeneratorInvert(BigDecimal x, BigDecimal theta) {
        frankBoundsConditions(theta);

        return CopulaConstants.MINUS_ONE
                .divide(theta, 16, RoundingMode.DOWN)
                .multiply(BigDecimal.valueOf(
                        Math.log(1 + Math.exp(-1 * x.doubleValue()) * (Math.exp(-1 * theta.doubleValue()) - 1))
                ));
    }

    static void joeBoundsConditions(BigDecimal theta) {
        if (theta.compareTo(BigDecimal.ONE) < 0) {
            throw new RuntimeException("The parameter of a Joe copula must be greater than 1.");
        }
    }

    static BigDecimal joeGenerator(BigDecimal x, BigDecimal theta) {
        boundsConditions(x);
        joeBoundsConditions(theta);

        return BigDecimal.valueOf(-1 * Math.log(1 - Math.pow(1 - x.doubleValue(), theta.doubleValue())));
    }

    static BigDecimal joeGeneratorInvert(BigDecimal x, BigDecimal theta) {
        joeBoundsConditions(theta);

        return BigDecimal.ONE
                .subtract(BigDecimal.valueOf(
                        Math.pow(1 - Math.exp(-1 * x.doubleValue()), BigDecimal.ONE.divide(theta, 16, RoundingMode.DOWN).doubleValue())));
    }

    static void aliMikhailHaqBoundsConditions(BigDecimal theta) {
        if (theta.compareTo(CopulaConstants.MINUS_ONE) < 0 || theta.compareTo(BigDecimal.ONE) >= 0) {
            throw new RuntimeException("The parameter of an Ali-Mikhail-Haq copula must be between -1 included and 1 excluded.");
        }
    }

    static BigDecimal aliMikhailHaqGenerator(BigDecimal x, BigDecimal theta) {
        boundsConditions(x);
        aliMikhailHaqBoundsConditions(theta);

        return BigDecimal.valueOf(
                Math.log(
                        BigDecimal.ONE
                                .subtract(theta.multiply(BigDecimal.ONE.subtract(x)))
                                .divide(x, 16, RoundingMode.DOWN)
                                .doubleValue()));
    }

    static BigDecimal aliMikhailHaqGeneratorInvert(BigDecimal x, BigDecimal theta) {
        aliMikhailHaqBoundsConditions(theta);

        return BigDecimal.ONE
                .subtract(theta)
                .divide(BigDecimal.valueOf(Math.exp(x.doubleValue())).subtract(theta), 16, RoundingMode.DOWN);
    }
}
